package com.kcht.np.common.filter;

import com.kcht.np.util.JsonUtils;
import com.kcht.np.util.RequestUtils;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;

/**
 * <p>
 * $
 * </p>
 *
 * @Author: liuqi
 * @CreateDate: 2020/4/27$ 10:42$
 */
@Slf4j
public class HttpTraceLogFilter extends OncePerRequestFilter implements Ordered {

    private static final String LOG_FORMATTER_IN = "请求路径：{%s}，执行时间：{%f} s，请求方法：{%s}，请求URL参数：%s，请求BODY参数：%s" +
            "，请求头参数：%s，路径参数：%s，来源IP：{%s}";
    private static final List<String> IGNORE_CONTENT_TYPE = new ArrayList<>(Arrays.asList("application/json"));

    private final MeterRegistry registry;

    public HttpTraceLogFilter(MeterRegistry registry) {
        this.registry = registry;
    }


    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE - 10;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (!isRequestValid(request)) {
            filterChain.doFilter(request, response);
            return;
        }
        if (!(request instanceof ContentCachingRequestWrapper)) {
            request = new ContentCachingRequestWrapper(request);
        }
        if (!(response instanceof ContentCachingResponseWrapper)) {
            response = new ContentCachingResponseWrapper(response);
        }
        String method = null;
        int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        long startTime = System.currentTimeMillis();
        try {
            filterChain.doFilter(request, response);
            method = request.getMethod();
            status = response.getStatus();
        } finally {
             String path = request.getRequestURI();
           if (checkFilter(request.getContentType())) {
               String requestBody = getRequestBody(request);
               //1. 记录日志
               double latency = System.currentTimeMillis() - startTime;
               //获取QueryString中的参数，GET方式
               Map<String, String[]> parameterMap = request.getParameterMap();
               //获取路径参数
               Map<String,String> uriTemplateMap = RequestUtils.getUriTemplateVar(request);
               //请求的真实IP
               String requestedIP = RequestUtils.getRealIP(request);
               Map<String, String> headerMap = null;
               if (log.isDebugEnabled()) {
                   headerMap = RequestUtils.getHeaders(request);
               }
               log.info(String.format(LOG_FORMATTER_IN, path, latency/1000, method, JsonUtils.objectToJson(parameterMap), requestBody,
                       headerMap, uriTemplateMap, requestedIP));
            }
            updateResponse(response);
        }
    }

    /**
     * 判断请求/返回是否为application/json
     * 是则进行拦截，
     * 否则退出
     * @param contentType 请求/响应类型
     */
    private boolean checkFilter(String contentType) {
        boolean filterFlag = false;
        //是否继续拦截
        for (String p : IGNORE_CONTENT_TYPE) {
            if (StringUtils.contains(contentType, p)){
                filterFlag = true;
            }
        }
        if (StringUtils.isEmpty(contentType)){
            filterFlag = true;
        }
        return filterFlag;
    }

    private boolean isRequestValid(HttpServletRequest request) {
        try {
            new URI(request.getRequestURL().toString());
            return true;
        } catch (URISyntaxException ex) {
            return false;
        }
    }

    private String getRequestBody(HttpServletRequest request) {
        String requestBody = "";
        ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            try {
                requestBody = IOUtils.toString(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
            } catch (IOException e) {
                // NOOP
            }
        }
        return requestBody;
    }

    private String getResponseBody(HttpServletResponse response) {
        String responseBody = "";
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {
            try {
                responseBody = IOUtils.toString(wrapper.getContentAsByteArray(), wrapper.getCharacterEncoding());
            } catch (IOException e) {
                // NOOP
            }
        }
        return responseBody;
    }

    private void updateResponse(HttpServletResponse response) throws IOException {
        ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        Objects.requireNonNull(responseWrapper).copyBodyToResponse();
    }


    @Data
    private static class HttpTraceLog {

        private String path;
        private String parameterMap;
        private String method;
        private Long timeTaken;
        private String time;
        private Integer status;
        private String requestBody;
        private String responseBody;
    }


}
